---
title: APIルートでフォームを構築する
description: JavaScriptを使ってフォーム送信をAPIルートへ送る方法を学びます。
i18nReady: true
type: recipe
---
import { Steps } from '@astrojs/starlight/components';
import UIFrameworkTabs from "~/components/tabs/UIFrameworkTabs.astro";
import PackageManagerTabs from "~/components/tabs/PackageManagerTabs.astro";

HTMLフォームはページを再読み込みしたり、別ページへ遷移したりします。代わりにAPIエンドポイントへフォームデータを送信するには、JavaScriptでフォーム送信をインターセプトする必要があります。

このレシピでは、フォームデータをAPIエンドポイントへ送信し、そのデータを処理する方法を説明します。

## 前提条件
- [オンデマンドレンダリング用のアダプター](/ja/guides/on-demand-rendering/)を導入したプロジェクト
- [UIフレームワークのインテグレーション](/ja/guides/framework-components/)をインストールしていること

## レシピ

<Steps>
1. フォームデータを受け取る`POST`のAPIエンドポイント`/api/feedback`を作成します。`request.formData()`で処理します。使用前に必ず値を検証します。

    
    この例ではメッセージを含むJSONオブジェクトをクライアントへ返します。
    
        ```ts title="src/pages/api/feedback.ts" "request.formData()" "post"
        export const prerender = false; // 「server」モードでは不要です
        import type { APIRoute } from "astro";

        export const POST: APIRoute = async ({ request }) => {
          const data = await request.formData();
          const name = data.get("name");
          const email = data.get("email");
          const message = data.get("message");
          // データのバリデーション — 実際にはこれ以上の検証を行う必要がある場合が多いです
          if (!name || !email || !message) {
            return new Response(
              JSON.stringify({
                message: "Missing required fields",
              }),
              { status: 400 }
            );
          }
          // データを処理して、成功レスポンスを返す
          return new Response(
            JSON.stringify({
              message: "Success!"
            }),
            { status: 200 }
          );
        };
        ```

2. 使用中のUIフレームワークでフォームコンポーネントを作成します。各入力には、その値の意味を表す`name`属性を付けます。

    
    必ず送信用の`<button>`または`<input type="submit">`要素を含めます。
    
    <UIFrameworkTabs>
      <Fragment slot="preact">
        
        ```tsx title="src/components/FeedbackForm.tsx"
        export default function Form() {
          return (
            <form>
              <label>
                Name
                <input type="text" id="name" name="name" required />
              </label>
              <label>
                Email
                <input type="email" id="email" name="email" required />
              </label>
              <label>
                Message
                <textarea id="message" name="message" required />
              </label>
              <button>Send</button>
            </form>
          );
        }
        ```
      </Fragment>
      <Fragment slot="react">
        
        ```tsx title="src/components/FeedbackForm.tsx"
        export default function Form() {
          return (
            <form>
              <label>
                Name
                <input type="text" id="name" name="name" required />
              </label>
              <label>
                Email
                <input type="email" id="email" name="email" required />
              </label>
              <label>
                Message
                <textarea id="message" name="message" required />
              </label>
              <button>Send</button>
            </form>
          );
        }
        ```
      </Fragment>
      <Fragment slot="solid">
        
        ```tsx title="src/components/FeedbackForm.tsx"
        export default function Form() {
          return (
            <form>
              <label>
                Name
                <input type="text" id="name" name="name" required />
              </label>
              <label>
                Email
                <input type="email" id="email" name="email" required />
              </label>
              <label>
                Message
                <textarea id="message" name="message" required />
              </label>
              <button>Send</button>
            </form>
          );
        }
        ```
      </Fragment>
      <Fragment slot="svelte">
        
        ```svelte title="src/components/FeedbackForm.svelte"
        <form>
          <label>
            Name
            <input type="text" id="name" name="name" required />
          </label>
          <label>
            Email
            <input type="email" id="email" name="email" required />
          </label>
          <label>
            Message
            <textarea id="message" name="message" required />
          </label>
          <button>Send</button>
        </form>
        ```
      </Fragment>
      <Fragment slot="vue">
        
        ```vue title="src/components/FeedbackForm.vue"
        <template>
          <form>
            <label>
              Name
              <input type="text" id="name" name="name" required />
            </label>
            <label>
              Email
              <input type="email" id="email" name="email" required />
            </label>
            <label>
              Message
              <textarea id="message" name="message" required />
            </label>
            <button>Send</button>
          </form>
        </template>
        ```
      </Fragment>

    </UIFrameworkTabs>

3. 送信イベントを受け取る関数を作成し、フォームの`submit`ハンドラーとして渡します。

    
    関数内では次を行います。
    - ブラウザーの既定の送信処理を上書きするために`preventDefault()`を呼びます。
    - `FormData`オブジェクトを作成し、`fetch()`で`POST`リクエストとしてエンドポイントへ送ります。
    
  

    <UIFrameworkTabs>
      <Fragment slot="preact">
        
        ```tsx title="src/components/FeedbackForm.tsx" "/api/feedback" add={1, 4-17, 34} add="onSubmit={submit}" "formData" "e.preventDefault();"
        import { useState } from "preact/hooks";

        export default function Form() {
          const [responseMessage, setResponseMessage] = useState("");

          async function submit(e: SubmitEvent) {
            e.preventDefault();
            const formData = new FormData(e.target as HTMLFormElement);
            const response = await fetch("/api/feedback", {
              method: "POST",
              body: formData,
            });
            const data = await response.json();
            if (data.message) {
              setResponseMessage(data.message);
            }
          }

          return (
            <form onSubmit={submit}>
              <label>
                Name
                <input type="text" id="name" name="name" required />
              </label>
              <label>
                Email
                <input type="email" id="email" name="email" required />
              </label>
              <label>
                Message
                <textarea id="message" name="message" required />
              </label>
              <button>Send</button>
              {responseMessage && <p>{responseMessage}</p>}
            </form>
          );
        }

        ```
      </Fragment>
      <Fragment slot="react">
        
        ```tsx title="src/components/FeedbackForm.tsx" "/api/feedback" add={1-2, 5-18, 35} add="onSubmit={submit}" "formData" "e.preventDefault();"
        import { useState } from "react";
        import type { FormEvent } from "react";

        export default function Form() {
          const [responseMessage, setResponseMessage] = useState("");

          async function submit(e: FormEvent<HTMLFormElement>) {
            e.preventDefault();
            const formData = new FormData(e.target as HTMLFormElement);
            const response = await fetch("/api/feedback", {
              method: "POST",
              body: formData,
            });
            const data = await response.json();
            if (data.message) {
              setResponseMessage(data.message);
            }
          }

          return (
            <form onSubmit={submit}>
              <label htmlFor="name">
                Name
                <input type="text" id="name" name="name" autoComplete="name" required />
              </label>
              <label htmlFor="email">
                Email
                <input type="email" id="email" name="email" autoComplete="email" required />
              </label>
              <label htmlFor="message">
                Message
                <textarea id="message" name="message" autoComplete="off" required />
              </label>
              <button>Send</button>
              {responseMessage && <p>{responseMessage}</p>}
            </form>
          );
        }
        ```
      </Fragment>
      <Fragment slot="solid">
        
        ```tsx title="src/components/FeedbackForm.tsx" "/api/feedback" add={1, 3-9, 13-19, 36} add="onSubmit={submit}" "formData" "e.preventDefault();"
        import { createSignal, createResource, Suspense } from "solid-js";

        async function postFormData(formData: FormData) {
          const response = await fetch("/api/feedback", {
            method: "POST",
            body: formData,
          });
          const data = await response.json();
          return data;
        }

        export default function Form() {
          const [formData, setFormData] = createSignal<FormData>();
          const [response] = createResource(formData, postFormData);

          function submit(e: SubmitEvent) {
            e.preventDefault();
            setFormData(new FormData(e.target as HTMLFormElement));
          }

          return (
            <form onSubmit={submit}>
              <label>
                Name
                <input type="text" id="name" name="name" required />
              </label>
              <label>
                Email
                <input type="email" id="email" name="email" required />
              </label>
              <label>
                Message
                <textarea id="message" name="message" required />
              </label>
              <button>Send</button>
              <Suspense>{response() && <p>{response().message}</p>}</Suspense>
            </form>
          );
        }

        ```
      </Fragment>
      <Fragment slot="svelte">
        
        ```svelte title="src/components/FeedbackForm.svelte" "/api/feedback" add={1-14, 30-32} add="on:submit={submit}" "formData" "e.preventDefault();"
        <script lang="ts">
          let responseMessage: string;

          async function submit(e: SubmitEvent) {
            e.preventDefault();
            const formData = new FormData(e.currentTarget as HTMLFormElement);
            const response = await fetch("/api/feedback", {
              method: "POST",
              body: formData,
            });
            const data = await response.json();
            responseMessage = data.message;
          }
        </script>

        <form on:submit={submit}>
          <label>
            Name
            <input type="text" id="name" name="name" required />
          </label>
          <label>
            Email
            <input type="email" id="email" name="email" required />
          </label>
          <label>
            Message
            <textarea id="message" name="message" required />
          </label>
          <button>Send</button>
          {#if responseMessage}
            <p>{responseMessage}</p>
          {/if}
        </form>
            ```
      </Fragment>
      <Fragment slot="vue">
        
        ```vue title="src/components/FeedbackForm.vue" "/api/feedback" add={1-16, 33} "formData" "e.preventDefault();"
        <script setup lang="ts">
        import { ref } from "vue";

        const responseMessage = ref<string>();

        async function submit(e: Event) {
          e.preventDefault();
          const formData = new FormData(e.currentTarget as HTMLFormElement);
          const response = await fetch("/api/feedback", {
            method: "POST",
            body: formData,
          });
          const data = await response.json();
          responseMessage.value = data.message;
        }
        </script>

        <template>
          <form @submit="submit">
            <label>
              Name
              <input type="text" id="name" name="name" required />
            </label>
            <label>
              Email
              <input type="email" id="email" name="email" required />
            </label>
            <label>
              Message
              <textarea id="message" name="message" required />
            </label>
            <button>Send</button>
            <p v-if="responseMessage">{{ responseMessage }}</p>
          </form>
        </template>
        ```
      </Fragment>

    </UIFrameworkTabs>

4. ページで`<FeedbackForm />`コンポーネントをインポートして配置します。必要なタイミングでロジックをハイドレートするため、必ず`client:*`ディレクティブを使います。
    
    <UIFrameworkTabs>
        <Fragment slot="preact">
        
        ```astro title="src/pages/index.astro" "client:load"
        ---
        import FeedbackForm from "../components/FeedbackForm"
        ---
        <FeedbackForm client:load />
        ```
        </Fragment>
        <Fragment slot="react">
        
        ```astro title="src/pages/index.astro" "client:load"
        ---
        import FeedbackForm from "../components/FeedbackForm"
        ---
        <FeedbackForm client:load />
        ```
        </Fragment>
        <Fragment slot="solid">
        
        ```astro title="src/pages/index.astro" "client:load"
        ---
        import FeedbackForm from "../components/FeedbackForm"
        ---
        <FeedbackForm client:load />
        ```
        </Fragment>
        <Fragment slot="svelte">
        
        ```astro title="src/pages/index.astro" "client:load"
        ---
        import FeedbackForm from "../components/FeedbackForm.svelte"
        ---
        <FeedbackForm client:load />
        ```
        </Fragment>
        <Fragment slot="vue">
        
        ```astro title="src/pages/index.astro" "client:load"
        ---
        import FeedbackForm from "../components/FeedbackForm.vue"
        ---
        <FeedbackForm client:load />
        ```
        </Fragment>
    </UIFrameworkTabs>
</Steps>

{/* ## 拡張: Zodでフォームを検証します

[Zod form data](https://www.npmjs.com/package/zod-form-data)は[Zod](https://github.com/colinhacks/zod)の上に構築されており、スキーマでフォームを検証できます。フィールドと要件を宣言し、検証をZodに任せられるためコードが簡潔になります。

1. `zod`と`zod-form-data`をインストールします。

    <PackageManagerTabs>
      <Fragment slot="npm">
        
        ```shell
          npm i zod zod-form-data
        ```
      </Fragment>
      <Fragment slot="pnpm">
        
        ```shell
          pnpm i zod zod-form-data
        ```
      </Fragment>
      <Fragment slot="yarn">
        
        ```shell
          yarn add zod zod-form-data
        ```
      </Fragment>
    </PackageManagerTabs>

2. APIルートのファイルで、`zfd.formData`を使ってスキーマを宣言してエクスポートします。 */}
